﻿using Microsoft.Xna.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using UCS.XNA.Utils;

namespace UCS.XNA.CombatBiplanes.Synchronization
{
    public abstract class GameSynchronizer
    {
        #region Constants

        protected const int SERVER_PORT = 2345;

        #endregion

        #region Private Members

        protected Dictionary<Player, TcpClient> _connectedClients;
        protected Dictionary<Player, byte[]> _playersBuffers;
        protected Dictionary<Player, int> _playersTotalBytesReceived;

        #endregion

        #region Attributes and Properties

        private CommandConverter _converter;
        public CommandConverter Converter
        {
            get { return this._converter; }
            set { this._converter = value; }
        }

        protected static GameSynchronizer _instance;
        public static GameSynchronizer Instance
        {
            get
            {
                return _instance;
            }
        }

        #endregion

        #region Events

        public event EventHandler<StringEventArgs> SocketError;

        #endregion

        #region Abstract Methods

        public abstract void Update(GameTime gameTime);
        public abstract void SendActionToEveryone(ActionType actionType, IActionExecutor executor, int[] parameters);
        protected abstract void SendActionToPlayer(Player player, ActionType actionType, IActionExecutor executor, int[] parameters);

        #endregion

        #region Public Methods

        public static void InitializeAsClient()
        {
            _instance = new ClientGameSynchronizer();
        }

        public static void InitializeAsServer(CombatBiplanes game, int maximumPlayers, CommandConverter converter)
        {
            _instance = new ServerGameSynchronizer(game, maximumPlayers, converter);
        }

        public static void DisposeInstance()
        {
            _instance = null;
        }

        public virtual void Stop()
        {
            foreach (TcpClient client in this._connectedClients.Values)
            {
                if (client != null)
                    client.Close();
            }
        }

        public virtual void Start()
        {
        }

        #endregion

        #region Protected Methods

        protected IEnumerable<byte[]> ReceiveCommand()
        {
            byte[] result = null;

            foreach (var playerClientPair in this._connectedClients)
            {
                Player player = playerClientPair.Key;
                TcpClient client = playerClientPair.Value;

                if (client != null)
                {
                    try
                    {
                        var stream = client.GetStream();
                        if (stream.DataAvailable)
                        {
                            byte[] responseBuffer = this._playersBuffers[player];
                            int totalBytesReceived = this._playersTotalBytesReceived[player];

                            try
                            {
                                var bytesReceived = stream.Read(responseBuffer, totalBytesReceived, responseBuffer.Length - totalBytesReceived);
                                totalBytesReceived += bytesReceived;
                            }
                            catch (IOException)
                            {
                                result = null;
                            }

                            if (totalBytesReceived >= responseBuffer.Length)
                            {
                                result = responseBuffer;

                                this.InitializeReceiveBuffer(player);
                            }
                            else
                            {
                                this._playersBuffers[player] = responseBuffer;
                                this._playersTotalBytesReceived[player] = totalBytesReceived;
                            }
                        }
                    }
                    catch (InvalidOperationException ex)
                    {
                        this.RaiseSocketError(ex.Message);
                    }
                }
            }

            yield return result;
        }

        protected void SendCommand(TcpClient client, byte[] command)
        {
            try
            {
                client.Client.Send(command);
            }
            catch (SocketException ex)
            {
                this.RaiseSocketError(ex.Message);
            }
        }

        protected void RaiseSocketError(string message)
        {
            if (this.SocketError != null)
            {
                StringEventArgs e = new StringEventArgs(message);
                this.SocketError(this, e);
            }
        }

        #endregion

        #region Private Methods

        private void InitializeReceiveBuffer(Player player)
        {
            this._playersBuffers[player] = new byte[5];
            this._playersTotalBytesReceived[player] = 0;
        }

        #endregion
    }
}
